﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using SkeletalAnimation3D;
using Animator3D.ObjectModel;
using System.ComponentModel;
using SkeletalAnimation3D.Core;

namespace Animator3D.Controls
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class Viewport2D : UserControl
    {
        public Viewport2D()
        {
            InitializeComponent();
            DataContextChanged += new DependencyPropertyChangedEventHandler(Viewport2D_DataContextChanged);
        }

        void Viewport2D_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue != null)
            {
                Document document = e.OldValue as Document;
                if (document != null)
                {
                    document.PropertyChanged -= new PropertyChangedEventHandler(Document_PropertyChanged);
                }
            }
            if (e.NewValue != null)
            {
                Document document = e.NewValue as Document;
                if (document != null)
                {
                    document.PropertyChanged += new PropertyChangedEventHandler(Document_PropertyChanged);
                }
            }
            Redraw();
        }

        void Document_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Redraw();
        }

        public int MinimalUnitsBetweenGridLines
        {
            get { return (int)GetValue(MinimalUnitsBetweenGridLinesProperty); }
            set { SetValue(MinimalUnitsBetweenGridLinesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinimalUnitsBetweenGridLines.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinimalUnitsBetweenGridLinesProperty =
            DependencyProperty.Register("MinimalUnitsBetweenGridLines", typeof(int), typeof(Viewport2D), new UIPropertyMetadata(5));

        public Point3D CameraPosition
        {
            get { return (Point3D)GetValue(CameraPositionProperty); }
            set { SetValue(CameraPositionProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CameraPosition.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CameraPositionProperty =
            DependencyProperty.Register("CameraPosition", typeof(Point3D), typeof(Viewport2D), new UIPropertyMetadata(new Point3D(0, 0, 0), CameraPositionChanged));

        private static void CameraPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue)
            {
                ((Viewport2D)d).Redraw();
            }
        }

        public CameraOrientations CameraOrientation
        {
            get { return (CameraOrientations)GetValue(CameraOrientationProperty); }
            set { SetValue(CameraOrientationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinorGridSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CameraOrientationProperty =
            DependencyProperty.Register("CameraOrientation", typeof(CameraOrientations), typeof(Viewport2D), new UIPropertyMetadata(CameraOrientations.Front, CameraOrientationChanged));


        private static void CameraOrientationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue)
            {
                ((Viewport2D)d).Redraw();
            }
        }

        public double CameraWidth
        {
            get { return (double)GetValue(CameraWidthProperty); }
            set { SetValue(CameraWidthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinorGridSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CameraWidthProperty =
            DependencyProperty.Register("CameraWidth", typeof(double), typeof(Viewport2D), new UIPropertyMetadata(5.0, CameraWidthChanged, CoerceCameraWidth));


        private static void CameraWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue)
            {
                ((Viewport2D)d).Redraw();
            }
        }

        private static object CoerceCameraWidth(DependencyObject d, object value)
        {
            Viewport2D v = (Viewport2D)d;
            double cameraWidth = (double)value;
            if (cameraWidth > 0)
            {
                return cameraWidth;
            }
            else
            {
                return v.CameraWidth;
            }
        }

        public double MinorGridSize
        {
            get { return (double)GetValue(MinorGridSizeProperty); }
            set { SetValue(MinorGridSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinorGridSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinorGridSizeProperty =
            DependencyProperty.Register("MinorGridSize", typeof(double), typeof(Viewport2D), new UIPropertyMetadata(0.1, MinorGridSizeChanged, CoerceMinorGridSize));

        private static void MinorGridSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue)
            {
                ((Viewport2D)d).Redraw();
            }
        }

        private static object CoerceMinorGridSize(DependencyObject d, object value)
        {
            Viewport2D v = (Viewport2D)d;
            double minorGridSize = (double)value;
            if (minorGridSize < v.MajorGridSize && minorGridSize > 0)
            {
                return minorGridSize;
            }
            else
            {
                return v.MinorGridSize;
            }
        }

        public double MajorGridSize
        {
            get { return (double)GetValue(MajorGridSizeProperty); }
            set { SetValue(MajorGridSizeProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MajorGridSize.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MajorGridSizeProperty =
            DependencyProperty.Register("MajorGridSize", typeof(double), typeof(Viewport2D), new UIPropertyMetadata(1.0, MajorGridSizeChanged, CoerceMajorGridSize));

        private static void MajorGridSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != e.OldValue)
            {
                ((Viewport2D)d).Redraw();
            }
        }

        private static object CoerceMajorGridSize(DependencyObject d, object value)
        {
            Viewport2D v = (Viewport2D)d;
            double majorGridSize = (double)value;
            if (majorGridSize > v.MinorGridSize && majorGridSize > 0)
            {
                return majorGridSize;
            }
            else
            {
                return v.MajorGridSize;
            }
        }

        public Color MinorGridColor
        {
            get { return (Color)GetValue(MinorGridColorProperty); }
            set { SetValue(MinorGridColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MinorGridColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MinorGridColorProperty =
            DependencyProperty.Register("MinorGridColor", typeof(Color), typeof(Viewport2D), new UIPropertyMetadata(Color.FromArgb(128, 55, 55, 55)));

        public Color MajorGridColor
        {
            get { return (Color)GetValue(MajorGridColorProperty); }
            set { SetValue(MajorGridColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MajorGridColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MajorGridColorProperty =
            DependencyProperty.Register("MajorGridColor", typeof(Color), typeof(Viewport2D), new UIPropertyMetadata(Color.FromArgb(128, 33, 33, 33)));

        public Color AxisColor
        {
            get { return (Color)GetValue(AxisColorProperty); }
            set { SetValue(AxisColorProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MajorGridColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty AxisColorProperty =
            DependencyProperty.Register("AxisColor", typeof(Color), typeof(Viewport2D), new UIPropertyMetadata(Color.FromArgb(178, 0, 0, 0)));
      
        public int SelectedBone
        {
            get { return (int)GetValue(SelectedBoneProperty); }
            set { SetValue(SelectedBoneProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedBones.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedBoneProperty =
            DependencyProperty.Register("SelectedBone", typeof(int), typeof(Viewport2D), new UIPropertyMetadata(-1, SelectedBonesChanged));



        public Point3DCollection WireFrame
        {
            get { return (Point3DCollection)GetValue(WireFrameProperty); }
            set { SetValue(WireFrameProperty, value); }
        }

        // Using a DependencyProperty as the backing store for WireFrame.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty WireFrameProperty =
            DependencyProperty.Register("WireFrame", typeof(Point3DCollection), typeof(Viewport2D), new UIPropertyMetadata(null, WireFrameChanged));


        public Bone Bones
        {
            get { return (Bone)GetValue(BonesProperty); }
            set { SetValue(BonesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Bones.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty BonesProperty =
            DependencyProperty.Register("Bones", typeof(Bone), typeof(Viewport2D), new UIPropertyMetadata(null, BonesChanged));

        public Int32Collection SelectedPointIds
        {
            get { return (Int32Collection)GetValue(SelectedPointIdsProperty); }
            set { SetValue(SelectedPointIdsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SelectedPointIds.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedPointIdsProperty =
            DependencyProperty.Register("SelectedPointIds", typeof(Int32Collection), typeof(Viewport2D), new UIPropertyMetadata(null, SelectedPointIdsChanged));


        public bool ShowAttachedPoints
        {
            get { return (bool)GetValue(ShowAttachedPointsProperty); }
            set { SetValue(ShowAttachedPointsProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ShowAttachedPoints.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ShowAttachedPointsProperty =
            DependencyProperty.Register("ShowAttachedPoints", typeof(bool), typeof(Viewport2D), new UIPropertyMetadata(false, ShowAttachedPointsChanged));



        private Point Project(Point3D point)
        {
            Point projectedPoint = new Point();
            double ratio = this.ActualWidth / this.CameraWidth;
            double cameraHeight = this.ActualHeight / ratio;

            switch (this.CameraOrientation)
            {
                case CameraOrientations.Top:
                    projectedPoint.X = point.X - CameraPosition.X + (this.CameraWidth / 2.0);
                    projectedPoint.Y = -point.Z - CameraPosition.Z + (cameraHeight / 2.0);
                    break;
                case CameraOrientations.Left:
                    projectedPoint.X = point.Z - CameraPosition.Z + (this.CameraWidth / 2.0);
                    projectedPoint.Y = point.Y - CameraPosition.Y + (cameraHeight / 2.0);
                    break;
                case CameraOrientations.Front:
                    projectedPoint.X = point.X - CameraPosition.X + (this.CameraWidth / 2.0);
                    projectedPoint.Y = point.Y - CameraPosition.Y + (cameraHeight / 2.0);
                    break;
                case CameraOrientations.Right:
                    projectedPoint.X = -point.Z - CameraPosition.Z + (this.CameraWidth / 2.0);
                    projectedPoint.Y = point.Y - CameraPosition.Y + (cameraHeight / 2.0);
                    break;
                case CameraOrientations.Bottom:
                    projectedPoint.X = point.X - CameraPosition.X + (this.CameraWidth / 2.0);
                    projectedPoint.Y = point.Z - CameraPosition.Z + (cameraHeight / 2.0);
                    break;
                case CameraOrientations.Back:
                    projectedPoint.X = -point.X - CameraPosition.X + (this.CameraWidth / 2.0);
                    projectedPoint.Y = point.Y - CameraPosition.Y + (cameraHeight / 2.0);
                    break;
                default:
                    throw new ArgumentException("Unknow camera position: " + this.CameraOrientation);
            }
            
            //scale
            projectedPoint.X *= ratio;
            projectedPoint.Y *= ratio;

            //Flip Y
            projectedPoint.Y = this.ActualHeight - projectedPoint.Y;
            return projectedPoint;
        }

        public Vector3D Unproject(Vector vector)
        {
            vector.Y = -vector.Y;
            double scaleRatio = this.ActualWidth / this.CameraWidth;
            Vector3D result = new Vector3D(0,0,0);
            switch (this.CameraOrientation)
            {
                case CameraOrientations.Top:
                    result.X = vector.X;
                    result.Z = -vector.Y;
                    break;
                case CameraOrientations.Left:
                    result.Z = vector.X;
                    result.Y = vector.Y;
                    break;
                case CameraOrientations.Front:
                    result.X = vector.X;
                    result.Y = vector.Y;
                    break;
                case CameraOrientations.Right:
                    result.Z = -vector.X;
                    result.Y = vector.Y;
                    break;
                case CameraOrientations.Bottom:
                    result.X = vector.X;
                    result.Z = vector.Y;
                    break;
                case CameraOrientations.Back:
                    result.X = -vector.X;
                    result.Y = vector.Y;
                    break;
                default:
                    throw new ArgumentException("Unknow camera position: " + this.CameraOrientation);
            }
            result /= scaleRatio;
            return result;
        }

        private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Redraw();
        }

        private static void WireFrameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2D v = d as Viewport2D;
            if (v != null)
            {
                v.Redraw();
            }
        }
        
        private static void ShowAttachedPointsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2D v = d as Viewport2D;
            if (v != null)
            {
                v.RedrawSelectedPoints();
                v.RedrawAttachedPoints();
            }
        }

        private static void SelectedBonesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2D v = d as Viewport2D;
            if (v != null)
            {
                v.RedrawBones();
                v.RedrawSelectedPoints();
                v.RedrawAttachedPoints();
            }
        }

        private static void SelectedPointIdsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2D v = d as Viewport2D;
            if (v != null)
            {
                v.RedrawSelectedPoints();
            }
        }

        private static void BonesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Viewport2D v = d as Viewport2D;
            if (v != null)
            {
                v.RedrawBones();
                v.RedrawSelectedPoints();
                v.RedrawAttachedPoints();
            }
        }

        private void Redraw()
        {
            RedrawGrid();
            RedrawMesh();
            RedrawBones();
            RedrawSelectedPoints();
            RedrawAttachedPoints();
        }

        private void RedrawAttachedPoints()
        {
            Document document = DataContext as Document;
            if (ShowAttachedPoints && SelectedBone >= 0 && document != null)
            {
                AttachedPointsContainer.Children.Clear();
                
                Int32Collection attachedPointIndices = document.GetAttachedPoints(SelectedBone);
                DoubleCollection attachedPointWeights = document.GetPointWeights(SelectedBone);
                if (attachedPointIndices != null && attachedPointWeights != null && attachedPointWeights.Count == attachedPointIndices.Count)
                {
                    for (int i = 0; i < attachedPointIndices.Count; i++)
                    {
                        int index = attachedPointIndices[i];
                        Point center = Project(document.GetMeshPointById(index));
                        Path path = new Path();
                        path.StrokeThickness = 0;
                        byte rank = Convert.ToByte(255 * attachedPointWeights[i]);
                        path.Fill = new SolidColorBrush(Color.FromRgb(rank, (byte)(255 - rank), 0));
                        RectangleGeometry rectangle = new RectangleGeometry(new Rect(center.X - 2, center.Y - 2, 5, 5));
                        path.Data = rectangle;
                        AttachedPointsContainer.Children.Add(path);
                    }
                    AttachedPointsContainer.Visibility = Visibility.Visible;
                }
            }
            else
            {
                AttachedPointsContainer.Visibility = Visibility.Collapsed;
            }
        }

        private void RedrawSelectedPoints()
        {
            Document document = DataContext as Document;
            if (document != null)
            {
                SelectedPointsContainer.Children.Clear();
                if (SelectedPointIds != null)
                {
                    for (int i = 0; i < SelectedPointIds.Count; i++)
                    {
                        int index = SelectedPointIds[i];
                        Point center = Project(document.GetMeshPointById(index));
                        Path path = new Path();
                        path.StrokeThickness = 0;
                        path.Fill = Brushes.Blue;
                        RectangleGeometry rectangle = new RectangleGeometry(new Rect(center.X - 4, center.Y - 4, 9, 9));
                        path.Data = rectangle;
                        SelectedPointsContainer.Children.Add(path);
                    }
                }
            }
        }

        private void RedrawGrid()
        {
            double scaleRatio = this.ActualWidth / this.CameraWidth;
            double cameraHeight = this.ActualHeight / scaleRatio;
            Point center = Project(this.CameraPosition);
            GridsContainer.Children.Clear();
            Path grid;
            GeometryGroup lines;
            if (MajorGridSize * scaleRatio >= MinimalUnitsBetweenGridLines)
            {
                grid = new Path();
                grid.Stroke = new SolidColorBrush(this.MajorGridColor);
                grid.StrokeThickness = 1;
                lines = new GeometryGroup();
                double deltaX;
                double deltaY;

                switch (CameraOrientation)
                {
                    case CameraOrientations.Front:
                        deltaX = Math.IEEERemainder(CameraPosition.X, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Y, MajorGridSize);
                        break;
                    case CameraOrientations.Back:
                        deltaX = Math.IEEERemainder(CameraPosition.X, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Y, MajorGridSize);
                        break;
                    case CameraOrientations.Left:
                        deltaX = Math.IEEERemainder(CameraPosition.Z, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Y, MajorGridSize);
                        break;
                    case CameraOrientations.Right:
                        deltaX = Math.IEEERemainder(CameraPosition.Z, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Y, MajorGridSize);
                        break;
                    case CameraOrientations.Top:
                        deltaX = Math.IEEERemainder(CameraPosition.X, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Z, MajorGridSize);
                        break;
                    case CameraOrientations.Bottom:
                        deltaX = Math.IEEERemainder(CameraPosition.X, MajorGridSize);
                        deltaY = Math.IEEERemainder(CameraPosition.Z, MajorGridSize);
                        break;
                    default:
                        throw new ApplicationException("Unknown Camera Orientation.");
                }

                double startX = (this.ActualWidth / 2) - deltaX * scaleRatio;
                double startY = (this.ActualHeight / 2) + deltaY * scaleRatio;
                double c = startX;
                while (c > 0)
                {
                    LineGeometry line = new LineGeometry(new Point((int)c + 0.5, 0), new Point((int)c + 0.5, GridsContainer.ActualHeight));
                    lines.Children.Add(line);
                    c -= MajorGridSize * scaleRatio;
                }
                c = startX + MajorGridSize * scaleRatio;
                while (c < this.ActualWidth)
                {
                    LineGeometry line = new LineGeometry(new Point((int)c + 0.5, 0), new Point((int)c + 0.5, GridsContainer.ActualHeight));
                    lines.Children.Add(line);
                    c += MajorGridSize * scaleRatio;
                }

                double r = startY;
                while (r > 0)
                {
                    LineGeometry line = new LineGeometry(new Point(0, (int)r + 0.5), new Point(this.ActualWidth, (int)r + 0.5));
                    lines.Children.Add(line);
                    r -= MajorGridSize * scaleRatio;
                }
                r = startY + MajorGridSize * scaleRatio;
                while (r < this.ActualHeight)
                {
                    LineGeometry line = new LineGeometry(new Point(0, (int)r + 0.5), new Point(this.ActualWidth, (int)r + 0.5));
                    lines.Children.Add(line);
                    r += MajorGridSize * scaleRatio;
                }

                grid.Data = lines;
                GridsContainer.Children.Add(grid);

                if (MinorGridSize * scaleRatio >= MinimalUnitsBetweenGridLines)
                {
                    grid = new Path();
                    grid.Stroke = new SolidColorBrush(this.MinorGridColor);
                    grid.StrokeThickness = 1;
                    lines = new GeometryGroup();

                    c = startX;
                    while (c > 0)
                    {
                        LineGeometry line = new LineGeometry(new Point((int)c + 0.5, 0), new Point((int)c + 0.5, GridsContainer.ActualHeight));
                        lines.Children.Add(line);
                        c -= MinorGridSize * scaleRatio;
                    }
                    c = startX + MinorGridSize * scaleRatio;
                    while (c < this.ActualWidth)
                    {
                        LineGeometry line = new LineGeometry(new Point((int)c + 0.5, 0), new Point((int)c + 0.5, GridsContainer.ActualHeight));
                        lines.Children.Add(line);
                        c += MinorGridSize * scaleRatio;
                    }

                    r = startY;
                    while (r > 0)
                    {
                        LineGeometry line = new LineGeometry(new Point(0, (int)r + 0.5), new Point(this.ActualWidth, (int)r + 0.5));
                        lines.Children.Add(line);
                        r -= MinorGridSize * scaleRatio;
                    }
                    r = startY + MinorGridSize * scaleRatio;
                    while (r < this.ActualHeight)
                    {
                        LineGeometry line = new LineGeometry(new Point(0, (int)r + 0.5), new Point(this.ActualWidth, (int)r + 0.5));
                        lines.Children.Add(line);
                        r += MinorGridSize * scaleRatio;
                    }

                    grid.Data = lines;
                    GridsContainer.Children.Add(grid);
                }
            }
            
            Point origin = Project(new Point3D(0, 0, 0));
            grid = new Path();
            grid.Stroke = new SolidColorBrush(this.AxisColor);
            grid.StrokeThickness = 1;
            lines = new GeometryGroup();
            LineGeometry xAxis = new LineGeometry(new Point(0.5, origin.Y), new Point(this.ActualWidth, origin.Y));
            lines.Children.Add(xAxis);
            LineGeometry yAxis = new LineGeometry(new Point(origin.X, 0.5), new Point(origin.X, this.ActualHeight));
            lines.Children.Add(yAxis);

            grid.Data = lines;
            GridsContainer.Children.Add(grid);
        }

        private void RedrawMesh()
        {
            ModelContainer.Children.Clear();
            Document document = DataContext as Document;
            if (document != null && WireFrame != null)
            {
                Path wireframe;
                GeometryGroup lines;

                wireframe = new Path();
                wireframe.Stroke = new SolidColorBrush(Colors.Black);
                wireframe.StrokeThickness = 1;
                lines = new GeometryGroup();
                LineGeometry triangleSide;
                for (int p = 0; p < WireFrame.Count; p += 3)
                {
                    triangleSide = new LineGeometry(Project(WireFrame[p]), Project(WireFrame[p + 1]));
                    lines.Children.Add(triangleSide);
                    triangleSide = new LineGeometry(Project(WireFrame[p + 1]), Project(WireFrame[p + 2]));
                    lines.Children.Add(triangleSide);
                    triangleSide = new LineGeometry(Project(WireFrame[p + 2]), Project(WireFrame[p]));
                    lines.Children.Add(triangleSide);
                }
                wireframe.Data = lines;
                ModelContainer.Children.Add(wireframe);
            }
        }

        private void RedrawBones()
        {
            BonesContainer.Children.Clear();
            SelectedBonesContainer.Children.Clear();

            if (Bones != null)
            {
                Path skeletonPath;
                Path selectedSkeletonPath;
                GeometryGroup skeletonGroup;
                GeometryGroup selectedSkeletonGroup;

                skeletonPath = new Path();
                selectedSkeletonPath = new Path();
                skeletonPath.Stroke = new SolidColorBrush(Colors.Blue);
                selectedSkeletonPath.Stroke = new SolidColorBrush(Colors.Red);
                skeletonPath.StrokeThickness = 1;
                selectedSkeletonPath.StrokeThickness = 1;
                skeletonGroup = new GeometryGroup();
                selectedSkeletonGroup = new GeometryGroup();

                DrawBone(null, Bones, skeletonGroup, selectedSkeletonGroup);
                
                skeletonPath.Data = skeletonGroup;
                selectedSkeletonPath.Data = selectedSkeletonGroup;
                BonesContainer.Children.Add(skeletonPath);
                SelectedBonesContainer.Children.Add(selectedSkeletonPath);
            }
        }

        private void DrawBone(Bone parentBone, Bone boneInfo, GeometryGroup bones, GeometryGroup selectedBones)
        {
            GeometryGroup jointTargetGroup = bones;
            GeometryGroup boneTargetGroup = bones;
            if (SelectedBone != -1 && SelectedBone == boneInfo.Id)
            {
                jointTargetGroup = selectedBones;
            }
            if (parentBone != null && SelectedBone != -1 && SelectedBone == parentBone.Id)
            {
                boneTargetGroup = selectedBones;
            }
            double scaleRatio = this.ActualWidth / CameraWidth;
            double boneRadius = boneInfo.Size / 2 * scaleRatio;
            
            EllipseGeometry joint;
            Point center = Project(boneInfo.Position);
            joint = new EllipseGeometry(center, boneRadius, boneRadius);
            jointTargetGroup.Children.Add(joint);
            if (parentBone != null)
            {
                double parentBoneRadius = parentBone.Size / 2 * scaleRatio;
                Point startPoint = center;
                Point endPoint = Project(parentBone.Position);
                double angle = Vector.AngleBetween(new Vector(0, 1), endPoint - startPoint);
                double length = (startPoint - endPoint).Length;
                LineGeometry boneToParent;
                boneToParent = new LineGeometry(startPoint, new Point(startPoint.X + parentBoneRadius, startPoint.Y + length));
                boneToParent.Transform = new RotateTransform(angle, center.X, center.Y);
                boneTargetGroup.Children.Add(boneToParent);
                boneToParent = new LineGeometry(startPoint, new Point(startPoint.X - parentBoneRadius, startPoint.Y + length));
                boneToParent.Transform = new RotateTransform(angle, center.X, center.Y);
                boneTargetGroup.Children.Add(boneToParent);
            }
            if (boneInfo.ChildBones != null)
            {
                for (int i = 0; i < boneInfo.ChildBones.Count; i++)
                {
                    DrawBone(boneInfo, boneInfo.ChildBones[i], bones, selectedBones);
                }
            }
        }

        

        public void MoveCamera(Vector offset)
        {
            double scaleRatio = this.ActualWidth / this.CameraWidth;
            Vector scaledOffset = -offset / scaleRatio;
            scaledOffset.X = -offset.X / scaleRatio;
            scaledOffset.Y = offset.Y / scaleRatio;
            switch (this.CameraOrientation)
            {
                case CameraOrientations.Top:
                    CameraPosition = new Point3D( CameraPosition.X + scaledOffset.X, CameraPosition.Y, CameraPosition.Z + scaledOffset.Y);
                    break;
                case CameraOrientations.Left:
                    CameraPosition = new Point3D(CameraPosition.X, CameraPosition.Y + scaledOffset.Y, CameraPosition.Z + scaledOffset.X);
                    break;
                case CameraOrientations.Front:
                    CameraPosition = new Point3D(CameraPosition.X + scaledOffset.X, CameraPosition.Y + scaledOffset.Y, CameraPosition.Z);
                    break;
                case CameraOrientations.Right:
                    CameraPosition = new Point3D(CameraPosition.X, CameraPosition.Y + scaledOffset.Y, CameraPosition.Z + scaledOffset.X);
                    break;
                case CameraOrientations.Bottom:
                    CameraPosition = new Point3D(CameraPosition.X + scaledOffset.X, CameraPosition.Y, CameraPosition.Z + scaledOffset.Y);
                    break;
                case CameraOrientations.Back:
                    CameraPosition = new Point3D(CameraPosition.X + scaledOffset.X, CameraPosition.Y + scaledOffset.Y, CameraPosition.Z);
                    break;
                default:
                    throw new ArgumentException("Unknow camera position: " + this.CameraOrientation);
            }
        }

        public void ShowSelector(Point corner1, Point corner2)
        {
            SelectorContainer.Visibility = Visibility.Visible;
            SelectionBox.Rect = new Rect(corner1, corner2);
        }

        public void HideSelector()
        {
            SelectorContainer.Visibility = Visibility.Hidden;
        }

        public void ShowDeselector(Point corner1, Point corner2)
        {
            DeselectorContainer.Visibility = Visibility.Visible;
            DeselectionBox.Rect = new Rect(corner1, corner2);
        }

        public void HideDeselector()
        {
            DeselectorContainer.Visibility = Visibility.Hidden;
        }


        public void ZoomCamera(double offset)
        {
            CameraWidth += offset;
        }

        public Point3D Unproject(Point point)
        {
            Point3D result = new Point3D(0, 0, 0);
            Vector screenOffset = new Vector(0,0);
            screenOffset.X = point.X - (this.ActualWidth / 2);
            screenOffset.Y = point.Y - (this.ActualHeight / 2);
            Vector3D offset = Unproject(screenOffset);
            result = CameraPosition + offset;
            return result;
        }

        public Int32Collection GetPointIdsInSelection()
        {
            return GetPointIdsInRectangle(SelectionBox.Rect);
        }

        public Int32Collection GetPointIdsInDeselection()
        {
            return GetPointIdsInRectangle(DeselectionBox.Rect);
        }

        private Int32Collection GetPointIdsInRectangle(Rect rectangle)
        {
            
            Int32Collection pointIds = new Int32Collection();
            Document document = DataContext as Document;
            if (document != null)
            {
                Point3DCollection points = document.MeshPoints;
                if (document != null && points != null)
                {
                    for (int i = 0; i < points.Count; i++)
                    {
                        Point point = Project(points[i]);
                        if (rectangle.Contains(point))
                        {
                            pointIds.Add(i);
                        }
                    }
                }
            }
            return pointIds;
        }
    }
}
